﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;
using RayDen.RayEngine.Core.Interface;

namespace RayDen.RayEngine.Core.Types
{

    public abstract class RayBufferBase<TRay, TRayHit> : IRayBufferBase
        where TRay : IRay
        where TRayHit : struct
    {

        public static int RayBufferSize = 65536;

        public const uint NotHit = 0xffffffffu;

        public int UserData;

        public TRay[] RaysInfo;
        public TRayHit[] rayHits;
        public int currentFreeRayIndex;
        public int size;
        protected readonly Stack<int> userData;


        public int RaysInitialized
        {
            get { return currentFreeRayIndex; }
        }


        protected RayBufferBase(int size)
        {
            currentFreeRayIndex = 0;
            this.RaysInfo = new TRay[size];
            this.rayHits = new TRayHit[size];
            this.size = size;
            this.userData = new Stack<int>();
        }

        public abstract bool IsHit(int index);

        public virtual void Reset()
        {
            this.currentFreeRayIndex = 0;
        }

        public int Size
        {
            get { return this.size; }
        }

        public bool IsFull
        {
            get
            {
                return currentFreeRayIndex >= size - 1;
            }
        }

        public int GetRayCount()
        {
            return currentFreeRayIndex;
        }


        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public int AddRay(TRay r)
        {
#if VERBOSE
            try {
#endif
            if (currentFreeRayIndex >= this.size)
            {
                throw new ArgumentException("Ray Buffer is full");
            }
            RaysInfo[currentFreeRayIndex] = r;
#if VERBOSE
            }
            catch(Exception ex) {
                Console.WriteLine(ex.Message);
            }
#endif
            return currentFreeRayIndex++;
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]

        public int AddRay(ref TRay r)
        {
            if (currentFreeRayIndex >= this.size)
            {
                throw new ArgumentException();
            }
            RaysInfo[currentFreeRayIndex] = r;
            return currentFreeRayIndex++;
        }
        public int ReserveRay()
        {
            return currentFreeRayIndex++;
        }

        public void Flush()
        {
#if VERBOSE
            GC.Collect();
#endif
        }

        public int LeftSpace()
        {
            return (size - 1) - currentFreeRayIndex;
        }

        protected void PushUserData(int data)
        {
            userData.Push(data);
        }

        protected int GetUserData()
        {
            return userData.Peek();
        }

        public int PopUserData()
        {
            int data = userData.Pop();
            return data;
        }

        public int GetUserDataCount()
        {
            return userData.Count;
        }

        public void ResetUserData()
        {
            userData.Clear();
        }
    }


    public class RayBuffer : RayBufferBase<RayData, RayHit>
    {


        public bool traced;

        public int HitCount
        {
            get { return this.rayHits.Count(item => item.Index != RayBuffer.NotHit); }
        }

        public int NonZeroHitCount
        {
            get
            {
                return this.rayHits.Count(item => item.Index != RayBuffer.NotHit & item.Index > 0);
            }
        }
        public IEnumerable<RayHit> NonZeroHits
        {
            get
            {
                //var query = rayHits.GroupBy(x => x)
                //                  .Where(g => g.Count() > 1)
                //                  .Select(y => y.Key)
                //                  .ToList();
                //var query = rayHits.GroupBy(x => x)
                //                  .Where(g => g.Count() > 1)
                //                  .Select(y => new { Element = y.Key, Counter = g.Count() })
                //                  .ToList();

                //return query;

                return this.rayHits.Where(item => item.Index != RayBuffer.NotHit & item.Index > 0);
            }
        }
        public RayBuffer(int size) : base(size) { }

        public override bool IsHit(int index)
        {
            return this.rayHits[index].Index != NotHit;
        }

        public override void Reset()
        {
            base.Reset();
            traced = false;
        }

        public bool IsBlocked(int index, int eyeRayIndex)
        {
            var rayHit = this.rayHits[index].Index != NotHit;
            var samePrim = this.rayHits[index].Index == this.rayHits[eyeRayIndex].Index;
            return rayHit && !samePrim;
        }

        public void GetHitPoint(int index, out Point result)
        {
            result = this.RaysInfo[index].Point(this.rayHits[index].Distance);
        }

    }

}
